from bcat.util.assert_or_raise import AssertOrRaise
from bcat.gtp.serde.error import ExpectTypeError, ValueEmptyError

from bcat.gtp.serde.escape import Escape
from bcat.gtp.serde.iserializer import ISerializer
from bcat.gtp.serde.writer import Writer

class Serializer(ISerializer):
    def __init__(self, writer):
        AssertOrRaise(writer).not_none()
        super().__init__(writer, 'gbk')

    def serialize(self, v):
        AssertOrRaise(v).not_none("v must be buffer").is_type(dict)

        if len(v) != 0:
            result = 0
            is_writed_some = False
            for key, value in v.items():
                writed_bytes_count = self.serialize_pair(key, value)
                if writed_bytes_count > 0: 
                    result += writed_bytes_count
                    result += self.serialize_pair_separator()
                    if not is_writed_some :
                        is_writed_some = True

            if is_writed_some :
                result -= self.writer().discard_back(1) # remove the last char:','
            return result
        else:
            raise ValueEmptyError("dict which is serialized to gtp is null")

    def serialize_pair(self, key, value):
        result = 0
        result += self.serialize_key(key)
        result += self.serialize_key_value_separator()

        writed_bytes_count = self.serialize_value(value)
        if writed_bytes_count > 0:
            result += writed_bytes_count
        else:
            self.writer().discard_back(result)
            result = 0

        return result

    def serialize_pair_separator(self):
        return self.writer().write_uint8(0x2C) # ascii => ','


    def serialize_key(self, key):
        result = Escape.from_str(key, self.encoding()).escape()
        return self.writer().write_bytes(result)
    def serialize_key_value_separator(self):
        return self.writer().write_uint8(0x3d) #ascii => '='
    def serialize_value(self, v):
        if v is None:
            return self.serialize_none()

        tp = type(v)
        if tp is str:
            return self.serialize_str(v)
        elif tp is int:
            return self.serialize_int(v)
        elif tp is float:
            return self.serialize_float(v)
        elif tp is dict:
            return self.serialize_map(v)
        elif tp in (list, tuple):
            return self.serialize_list(v)
        elif tp is bool:
            return self.serialize_bool(v)
        elif tp in (bytes, bytearray, memoryview):
            return self.serialize_bytes(v)
        else:
            raise TypeError("not support type tto serialize :{}".format(tp))

    def serialize_none(self):
        return 0

    def serialize_bool(self, v):
        return self.writer().write_bool(v)
    def serialize_str(self, v):
        result = Escape.from_str(v, self.encoding()).escape()
        return self.writer().write_bytes(result)
    def serialize_bytes(self, v):
        result = Escape.from_bytes(v).escape()
        return self.writer().write_bytes(result)
    def serialize_int(self, v):
        return self.writer().write_str(str(v), self.encoding())
    def serialize_float(self, v):
        return self.writer().write_str(str(v), self.encoding())

    def serialize_map_start(self):
        return self.writer().write_uint8(0x7B) # ascii => '{'
    def serialize_map_end(self):
        return self.writer().write_uint8(0x7D) # ascii => '}'

    def serialize_map(self, v):
        if type(v) is dict:
            result = 0
            if len(v) != 0:
                is_writed_some = False
                result += self.serialize_map_start()

                for key, value in v.items():
                    writed_bytes_count = self.serialize_pair(key, value)
                    if writed_bytes_count > 0:
                        result += self.serialize_pair_separator()
                        if not is_writed_some :
                            is_writed_some = True

                if is_writed_some: 
                    result -= self.writer().discard_back(1) # discard_back pair sep:','
                    result += self.serialize_map_end()
                else:  # all values is-empty -> discard_back map-start:'{'
                    result -= self.writer().discard_back(1)

            else:  # map is null, ignore
                pass
            return result
        else:
            raise ExpectTypeError.from_real_value(dict, v)

    def serialize_list_start(self):
        return self.writer().write_uint8(0x5B) # ascii => '['
    def serialize_list_end(self):
        return self.writer().write_uint8(0x5D) # ascii => ']'

    def serialize_list(self, v):
        if type(v) is list:
            result = 0
            if len(v) > 0:
                is_writed_some = False
                result += self.serialize_list_start()

                for e in v: 
                    writed_bytes_count = self.serialize_value(e)
                    if writed_bytes_count > 0:
                        result += writed_bytes_count
                        result += self.serialize_pair_separator()
                        if not is_writed_some:
                            is_writed_some = True
                if is_writed_some:
                    result -= self.writer().discard_back(1) #discard_back pair sep:','
                    result += self.serialize_list_end()

                else: # all value is-empty -> discard_back list-start: '['
                    result -= self.writer().discard_back(1)

            else: # list is null, ignore
                pass
            return result
        else:
            raise ExpectTypeError.from_real_value(list, v)

        



import unittest
from bcat.gtp.serde.ser.bytes_writer import BytesWriter
class TestSerializer(unittest.TestCase):
    #@unittest.skip("test_main")
    def test_main(self):
        src = {
            "hello": "w,or\\ld",
            "hell1" : b"={hh}x\\hx",
            "hell2" : "ddhehehhxhx",
            "hello3" : {
                "w1" : 23,
                "w2" : 23,
                "w3" : 23,
            },
            "hell4" : [
                "string1",
                "string2",
                "string2",
            ],
            "hello5" : b'bytes:ddddddddddd',
            "hello6" : None,
        }
        writer = BytesWriter.default()
        Serializer(writer).serialize(src)
        print(writer.target().decode())

        

class TestSerializeEmptyValue(unittest.TestCase):
    def __assert_serialize_empty_value(self, src):
        writer = BytesWriter.default()
        self.assertEqual(0, Serializer(writer).serialize(src))

    def test_except(self):
        src = {}
        writer = BytesWriter.default()
        with self.assertRaises(ValueEmptyError):
            Serializer(writer).serialize(src)

    def test_empty_str(self):
        self.__assert_serialize_empty_value({"hello1" : None})
        self.__assert_serialize_empty_value({"hello": ''})

    def test_empty_bytes(self):
        self.__assert_serialize_empty_value({ "hello2" : bytes() })
        self.__assert_serialize_empty_value({ "hello2" : bytearray()})

    def test_empty_array(self):
        self.__assert_serialize_empty_value({ "hello2" : [] })
        self.__assert_serialize_empty_value({
            "hello2" : [
                '',
                None,
                bytes(),
                '',
                '',
            ],
        })
        self.__assert_serialize_empty_value({
            'hello' : [
                {},
                {},
                {},
            ]
        })

    def test_empty_map(self):
        self.__assert_serialize_empty_value({
            "map-key": {}
        })

        self.__assert_serialize_empty_value({
            "map-key": {
                "hello": '',
                "hello1": None,
            }
        })
        self.__assert_serialize_empty_value({ 
            "hello": {}, 
            "hello1" : [] , 
            "hello2" : "",
            "hello3" : bytes(),
            "hello4" : None
        })


if __name__ == '__main__':
    unittest.main(verbosity=2)
